#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import re
import sys
import signal
from ftplib import FTP
from syslog import *
from threading import Event
from contextlib import closing
from traceback import format_exc
from os.path import abspath, join
from optparse import OptionParser


def become_daemon(out="/dev/null", err="/dev/null", home_dir=".", umask=022):
    # First fork
    try: 
        if os.fork() > 0: 
            sys.exit(0)
    except OSError, e:
        sys.stderr.write("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror))
        sys.exit(1)
    os.setsid()
    os.chdir(home_dir)
    os.umask(umask)

    # Second fork
    try: 
        if os.fork() > 0: 
            os._exit(0)
    except OSError, e:
        sys.stderr.write("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror))
        os._exit(1)
    si = open('/dev/null', 'r') 
    so = open(out, 'a+', 0)
    se = open(err, 'a+', 0)
    os.dup2(si.fileno(), sys.stdin.fileno())
    os.dup2(so.fileno(), sys.stdout.fileno())
    os.dup2(se.fileno(), sys.stderr.fileno())
    # Set custom file descriptors so that they get proper buffering.
    sys.stdout, sys.stderr = so, se


class Uploader:
    def __init__(self, src, dst):
        self.src = abspath(src)
        self.dst = dst
        re_dst = re.compile(r"ftp://(.+?):(.+?)@(.+?)/(.+)")
        self.user, self.passwd, self.host, self.path = re_dst.match(self.dst).groups()
        self._stop_event = Event()

    def sync(self):
        with closing(FTP(self.host, self.user, self.passwd, self.user, 2)) as ftp:
            paths = self.path.split("/")
            for i in range(1, len(paths)+1):
                try:
                    ftp.mkd("/".join(paths[:i]))
                except:
                    #syslog(LOG_DEBUG, format_exc())
                    pass
            ftp.cwd(self.path)
            length = len(self.src)
            for dirpath, dirnames, filenames in os.walk(self.src):
                dpath = dirpath[length+1:]
                try:
                    ftp.mkd(dpath)
                except:
                    #syslog(LOG_DEBUG, format_exc())
                    pass
                for filename in filenames:
                    local_filepath = join(dirpath, filename)
                    filepath = local_filepath[length+1:]
                    with open(local_filepath, "rb") as fp:
                        ftp.storbinary("stor %s.tmp"%filepath, fp)
                    ftp.rename(filepath+".tmp", filepath)
                    #os.remove(local_filepath)

    def start(self):
        signal.signal(signal.SIGINT, self.stop)
        signal.signal(signal.SIGTERM, self.stop)
        while not self._stop_event.isSet():
            try:
                self.sync()
            except:
                syslog(LOG_DEBUG, format_exc())
            self._stop_event.wait(options.interval)

    def stop(self, signum, frame):
        self._stop_event.set()


if __name__ == "__main__":
    parser = OptionParser(usage="usage: %prog [-dq] <src> <dst>")
    parser.add_option("-d", "--daemon",
            dest="daemon",
            action="store_true",
            default=False,
            help="run the script as a daemon")
    parser.add_option("-q", "--quiet",
            dest="quiet",
            action="store_true",
            default=False,
            help="don't print log message")
    parser.add_option("-s", "--interval=",
            dest="interval",
            metavar="seconds",
            type="int",
            default=60,
            help="the interval seconds between scaning the server")
    options, args = parser.parse_args()
    if options.daemon:
        become_daemon()
    mask = LOG_CONS|LOG_PID
    if not options.quiet:
        mask = mask|LOG_PERROR
    openlog("ftpsync", mask)
    if len(args) != 2:
        parser.print_help()
        sys.exit(1)
    uploader = Uploader(args[0], args[1])
    uploader.start()
